/******************************************************************************* * Copyright 2013 Pawel Pastuszak * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. ******************************************************************************/ package pl.kotcrab.modelviewer; import java.awt.BorderLayout; import java.awt.EventQueue; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.util.ArrayList; import java.util.Iterator; import javax.swing.JCheckBoxMenuItem; import javax.swing.JFileChooser; import javax.swing.JFrame; import javax.swing.JMenu; import javax.swing.JMenuBar; import javax.swing.JMenuItem; import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.JSpinner; import javax.swing.SpinnerNumberModel; import javax.swing.UIManager; import javax.swing.UnsupportedLookAndFeelException; import javax.swing.filechooser.FileNameExtensionFilter; import com.badlogic.gdx.ApplicationListener; import com.badlogic.gdx.Gdx; import com.badlogic.gdx.assets.AssetManager; import com.badlogic.gdx.backends.lwjgl.LwjglCanvas; import com.badlogic.gdx.graphics.Color; import com.badlogic.gdx.graphics.GL10; import com.badlogic.gdx.graphics.PerspectiveCamera; import com.badlogic.gdx.graphics.VertexAttributes.Usage; import com.badlogic.gdx.graphics.g2d.BitmapFont; import com.badlogic.gdx.graphics.g2d.SpriteBatch; import com.badlogic.gdx.graphics.g3d.Environment; import com.badlogic.gdx.graphics.g3d.Material; import com.badlogic.gdx.graphics.g3d.Model; import com.badlogic.gdx.graphics.g3d.ModelBatch; import com.badlogic.gdx.graphics.g3d.ModelInstance; import com.badlogic.gdx.graphics.g3d.attributes.ColorAttribute; import com.badlogic.gdx.graphics.g3d.environment.DirectionalLight; import com.badlogic.gdx.graphics.g3d.utils.AnimationController; import com.badlogic.gdx.graphics.g3d.utils.CameraInputController; import com.badlogic.gdx.graphics.g3d.utils.ModelBuilder; import com.badlogic.gdx.math.Matrix4; public class ModelViewer { private JFrame frame; final JFileChooser fc = new JFileChooser(); /** * Curent model path */ private String currentPath = null; /** * List of waiting events (added by frame, processed by renderer) */ public static final ArrayList<Event> eventList = new ArrayList<Event>(); private boolean optRenderLines = true; private boolean optRenderText = true; private boolean optRenderLight = true; private JSpinner animSpinner; /** * Stop animation */ private JMenuItem btnStop; /** * Play animation */ private JMenuItem btnPlay; /** * Launch the application. */ public static void main(String[] args) { //change lool and feel try { UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException e1) { e1.printStackTrace(); } EventQueue.invokeLater(new Runnable() { public void run() { try { ModelViewer window = new ModelViewer(); window.frame.setVisible(true); } catch (Exception e) { e.printStackTrace(); } } }); } /** * Create the application. */ public ModelViewer() { initialize(); } /** * Initialize the contents of the frame. */ private void initialize() { frame = new JFrame("Libgdx 3D Model Viewer"); frame.setBounds(100, 100, 659, 451); frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE); frame.getContentPane().setLayout(new BorderLayout(0, 0)); JPanel renderPanel = new JPanel(new BorderLayout()); frame.getContentPane().add(renderPanel, BorderLayout.CENTER); final LwjglCanvas canvas = new LwjglCanvas(new Renderer(), true); //create canvas used for rendering renderPanel.add(canvas.getCanvas()); //atach canvas to panel JMenuBar menuBar = new JMenuBar(); frame.getContentPane().add(menuBar, BorderLayout.NORTH); JMenu mnFileMenu = new JMenu("File"); menuBar.add(mnFileMenu); fc.setAcceptAllFileFilterUsed(false); fc.addChoosableFileFilter(new FileNameExtensionFilter("OBJ or G3DB model", "obj", "OBJ", "g3db", "G3DB")); JMenuItem mntmNewMenuItem = new JMenuItem("Load"); mntmNewMenuItem.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { int returnVal = fc.showOpenDialog(frame); if (returnVal == JFileChooser.APPROVE_OPTION) { eventList.add(new Event(fc.getSelectedFile().getAbsolutePath().replace('\\', '/'), EventType.CHANGEFILE)); } } }); mnFileMenu.add(mntmNewMenuItem); JMenu mnRenderMenu = new JMenu("Render"); menuBar.add(mnRenderMenu); final JCheckBoxMenuItem chckRenderLines = new JCheckBoxMenuItem("Lines"); chckRenderLines.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { optRenderLines = chckRenderLines.isSelected(); } }); final JCheckBoxMenuItem chckRenderLight = new JCheckBoxMenuItem("Light"); chckRenderLight.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { optRenderLight = chckRenderLight.isSelected(); } }); final JCheckBoxMenuItem chckRenderText = new JCheckBoxMenuItem("Text"); chckRenderText.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { optRenderText = chckRenderText.isSelected(); } }); chckRenderLight.setSelected(true); chckRenderLines.setSelected(true); chckRenderText.setSelected(true); mnRenderMenu.add(chckRenderLight); mnRenderMenu.add(chckRenderLines); mnRenderMenu.add(chckRenderText); JMenu mnAnimation = new JMenu("Animation"); menuBar.add(mnAnimation); animSpinner = new JSpinner(); mnAnimation.add(animSpinner); btnPlay = new JMenuItem("Play"); btnPlay.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { eventList.add(new Event(null, EventType.PLAYANIM)); } }); mnAnimation.add(btnPlay); btnStop = new JMenuItem("Stop"); btnStop.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { eventList.add(new Event(null, EventType.STOPANIM)); } }); mnAnimation.add(btnStop); JMenu mnModel = new JMenu("Model"); menuBar.add(mnModel); JMenuItem btnModelScale = new JMenuItem("Scale"); btnModelScale.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { float scale; try { scale = Float.parseFloat(JOptionPane.showInputDialog(null, "Please enter new scale: ")); } catch (NumberFormatException e1) //if entered text is not number { JOptionPane.showMessageDialog(null, "Enter number!"); return; } if (scale <= 0) { JOptionPane.showMessageDialog(null, "Wrong number!"); return; } eventList.add(new Event("" + scale, EventType.SCALE)); } }); mnModel.add(btnModelScale); } /** * Rendering class * */ class Renderer implements ApplicationListener { //camera private PerspectiveCamera cam; private CameraInputController camController; //models private ModelBatch modelBatch; private ModelInstance instance; private AssetManager assets; private boolean loading; //things for 2d rendering private SpriteBatch batch; private Matrix4 viewMatrix; private BitmapFont font; //lines private ModelBuilder modelBuilder; private ModelInstance xi; private ModelInstance yi; private ModelInstance zi; private Model x; private Model y; private Model z; private Environment lights; //animation private AnimationController animContrl; private boolean animPlaying; @Override public void create() { modelBatch = new ModelBatch(); lights = new Environment(); lights.set(new ColorAttribute(ColorAttribute.AmbientLight, 0.4f, 0.4f, 0.4f, 1f)); lights.add(new DirectionalLight().set(0.8f, 0.8f, 0.8f, -10f, -8f, -2f)); cam = new PerspectiveCamera(67, 400, 300); cam.position.set(1f, 1f, 1f); cam.lookAt(0, 0, 0); cam.near = 0.1f; cam.far = 300f; cam.update(); camController = new CameraInputController(cam); Gdx.input.setInputProcessor(camController); //2d stuff batch = new SpriteBatch(); viewMatrix = new Matrix4().setToOrtho2D(0, 0, Gdx.graphics.getWidth(), Gdx.graphics.getHeight()); font = new BitmapFont(Gdx.files.internal("data/arial-15.fnt"), Gdx.files.internal("data/arial-15_00.png"), false); //load startup model assets = new AssetManager(); assets.load("data/ship.obj", Model.class); currentPath = "data/ship.obj"; loading = true; //lines modelBuilder = new ModelBuilder(); x = modelBuilder.createBox(0.01f, 0.01f, 300f, new Material(ColorAttribute.createDiffuse(Color.BLUE)), Usage.Position | Usage.Normal); xi = new ModelInstance(x); xi.transform.setToTranslation(0, 0, 150); y = modelBuilder.createBox(0.01f, 0.01f, 300f, new Material(ColorAttribute.createDiffuse(Color.RED)), Usage.Position | Usage.Normal); yi = new ModelInstance(y); Matrix4 transform = new Matrix4().setToTranslation(150, 0, 0); Matrix4 rotate = new Matrix4().setToRotation(0, 1, 0, 90); yi.transform = transform.mul(rotate); z = modelBuilder.createBox(0.01f, 0.01f, 300f, new Material(ColorAttribute.createDiffuse(Color.GREEN)), Usage.Position | Usage.Normal); zi = new ModelInstance(z); transform = new Matrix4().setToTranslation(0, 150, 0); rotate = new Matrix4().setToRotation(1, 0, 0, 90); zi.transform = transform.mul(rotate); } /** * Assets manager finished loading models */ private void doneLoading() { Model newModel = assets.get(currentPath, Model.class); instance = new ModelInstance(newModel); loading = false; //setting spinner max values if (instance.animations.size == 0) //0 if no animation in loaded model animSpinner.setModel(new SpinnerNumberModel(0, 0, 0, 1)); else animSpinner.setModel(new SpinnerNumberModel(0, 0, instance.animations.size - 1, 1)); //if thers no animation deactivte play and stop button if (instance.animations.size == 0) { btnPlay.setEnabled(false); btnStop.setEnabled(false); } else { btnPlay.setEnabled(true); btnStop.setEnabled(true); } frame.setSize(frame.getWidth(), frame.getWidth()); animContrl = new AnimationController(instance); animPlaying = false; } @Override public void resize(int width, int height) { viewMatrix.setToOrtho2D(0, 0, Gdx.graphics.getWidth(), Gdx.graphics.getHeight()); //update matrix for 2d } /** * Process all waiting events */ public void processEvents() { Iterator<Event> itr = eventList.iterator(); while (itr.hasNext()) { Event e = itr.next(); if (e.eventType == EventType.CHANGEFILE) { assets.load(e.eventInfo, Model.class); loading = true; currentPath = e.eventInfo; itr.remove(); } if (e.eventType == EventType.PLAYANIM) { animPlaying = true; //using selected animation (from spinner), with loop count equal to Integer.MAX_VALUE animContrl.animate(instance.animations.get((int) animSpinner.getValue()).id, Integer.MAX_VALUE, 1, null, 0); itr.remove(); } if (e.eventType == EventType.STOPANIM) { //don't know how to stop animation, just set loop count to 1 and animation will be stoped automaticly when it's finished animContrl.current.loopCount = 1; itr.remove(); } if (e.eventType == EventType.SCALE) { float scale = Float.parseFloat(e.eventInfo); //resource: http://www.physics.nyu.edu/grierlab/idl_html_help/obj_transform8.html float[] val = { scale, 0, 0, 0, 0, scale, 0, 0, 0, 0, scale, 0, 0, 0, 0, 1 }; Matrix4 scaleMatrix = new Matrix4().set(val); instance.transform.mul(scaleMatrix); itr.remove(); } } } @Override public void render() { //event is waiting if (eventList.size() > 0) processEvents(); //process it //loading models if finished if (loading && assets.update()) doneLoading(); if (animContrl != null && animPlaying) { if (animContrl.current.loopCount == 0) animPlaying = false; animContrl.update(Gdx.graphics.getDeltaTime()); } Gdx.gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT); cam.update(); if (optRenderText) { batch.setProjectionMatrix(viewMatrix); batch.begin(); font.draw(batch, "FPS: " + Gdx.graphics.getFramesPerSecond(), 5, Gdx.graphics.getHeight() - 5); if (instance != null) { font.draw(batch, "Materials: " + instance.materials.size, 5, Gdx.graphics.getHeight() - 20); font.draw(batch, "Animations: " + instance.animations.size, 5, Gdx.graphics.getHeight() - 35); } batch.end(); } camController.update(); modelBatch.begin(cam); if (instance != null) { if (optRenderLight) modelBatch.render(instance, lights); else modelBatch.render(instance); } if (optRenderLines) { modelBatch.render(xi); modelBatch.render(yi); modelBatch.render(zi); } modelBatch.end(); } @Override public void pause() { } @Override public void resume() { } @Override public void dispose() { //this create exception "No OpenGL context found in the current thread." don't now why and how to fix it. // x.dispose(); // y.dispose(); // z.dispose(); // modelBatch.dispose(); } } }